/**
 * 节流: 对于连续的函数调用,一段时间内只让其执行一次
 *
 * @param fn               需要被节流的函数
 * @param wait             节流时长(指定节流时长内只会执行一次func)
 * @param options
 * @param options.leading  默认为true,第一次会立即执行
 * @param options.trailing 默认为true,所有非"第一次"执行都会延时执行(为false时是立即执行),并且最后表现效果为停止后仍会多执行一次
 *
 */
const throttle = (fn, wait, options = {leading: true,trailing: true}) => {
  let
    // 上一次目标函数有效执行时间
    methodPrevious = null,

    // 上一次被节流的目标函数触发时间
    /*
      当leading为true,trailing为false时, 其表现行为我们期望为: 第一次执行时是立即执行的, 后面都是延时执行的
      在这里 我们需要区分 我们是一直不停滚动 然后到了第二个节流时间点 触发的目标函数第二次执行
      还是 滚动过后停止了一段时候 再次滚动触发的目标函数执行
      前者我们需要让其 延时执行
      后者我们需要让其 立即执行
      这里关键点在于 我们如何知道 我们是否停止过一段时间触发目标函数
      这里我们通过 throttledPrevious 这个参数是否大于某个预设值来判断是否停止过一段时间的触发
    */
    throttledPrevious = null,

    timeout = null,
    context = null,
    args = null;

  //防止内存泄漏(methodPrevious、throttledPrevious等是基础类型,不用担心)
  const clear = () => {
    context = null;
    args = null;
    timeout = null;
  };

  const throttledFn = function () {
    context = this;
    args = arguments; //使用arguments而不是使用剩余参数,是方便在外面调用清理该内存

    return new Promise(resolve => {
      const now = Date.now();
      const throttledInterval = throttledPrevious ? now - throttledPrevious : null;
      throttledPrevious = now;

      const runNow = () => {
        clearTimeout(timeout);
        resolve(fn.apply(context, args));
        clear();
        methodPrevious = now;
      };
      const runLater = () => {
        methodPrevious = now;

        //定时器可能因为这样那样的原因 并没有准时执行
        //但已经到了下一个时间段了 此时就让其先立即执行一次
        if(timeout) runNow();

        timeout = setTimeout(() => {
          resolve(fn.apply(context, args));
          clear();
        }, wait);
      };

      //一个节流时段内第一次触发
      if (
        methodPrevious === null && throttledInterval === null //整个程序声明周期中 目标函数第一次触发
        || throttledInterval > 500 //滚动过后停止了一段时候 再次滚动第一次触发的目标函数执行
      ) {
        if(options.leading) return runNow();
        runLater();

      //非第一次
      }else if (now - methodPrevious > wait) {
        if(!options.trailing) return runNow();
        runLater();
      }
    })
  };

  throttledFn.cancel = function() {
    clearTimeout(timeout);
    clear();
  }

  return throttledFn;
};

export default throttle;
